package structybxb

import "fmt"

// 结构体的声明：type <结构体名> struct {}
// 定义一个结构体，结构体如果需要跨包调用，则结构体的名字首字母需要大写，否则小写即可。
// 结构体属性，能大写则全部大写。因为后续涉及到一些三方包(例如：encoding/json)的使用时，需要用到属性。
type person struct {
	Name string
	City string
	Age  int
}

// 定义结构体person的方法，类似于JAVA中，类的行为。
// (p person):叫接收器，或接收者。接收器会接收调用对象
// 实例化对象调用该方法时，会将实例化对象作为参数传入到方法中接收器(p person)中，然后就可以使用接收器变量使用属性。
func (p person /* 值类型接收器 */) GetInfo() { // 值类型接收器
	fmt.Printf("p的名字是:%s, 城市是:%s, 年龄是:%d\n", p.Name, p.City, p.Age)
}

// 定义结构体person的方法：值类型接收器
// 注意:接收器，接收到的参数是值传递(值传递是值的复制-copy)
func (p person) /* 值类型接收器 */ SetInfo1(name string, city string, age int) {
	fmt.Printf("SetInfo1方法中,p的名字是:%s, 城市是:%s, 年龄是:%d\n", p.Name, p.City, p.Age)
	p.Name = name
	p.City = city
	p.Age = age
}

// 定义结构体person的方法：指针接收器
func (p *person) /* 指针类型接收器 */ SetInfo2(name string, city string, age int) { // 指针类型接收器
	fmt.Printf("SetInfo2方法中,p的名字是:%s, 城市是:%s, 年龄是:%d\n", p.Name, p.City, p.Age)
	p.Name = name
	p.City = city
	p.Age = age
}

/**
注意：
 普通的值类型接收器和指针类型的接收器之间的区别：
 接收器的参数传递时，使用的值传递(值的copy)。所以：值类型接收器复制的值本身，而指针接收器复制的指针(内存地址)
*/

// 方式1:声明结构体：普通类型隐式初始化
// 注意：Go语言中，结构体是基本数据类型，是值引用。
func TestStructDefine1() {
	// 结构体的实例化
	var p1 person // 注意：没有显式的初始化,也不会报错。原因是:person结构体的属性，会自动初始化为零值。
	// 在Go语言中，声明结构体变量时如果没有显式初始化，编译器会自动将其初始化为对应类型的零值。
	// 对于person结构体来说，Name和City会被初始化为空字符串"", Age会被初始化为0。
	fmt.Printf("未初始化的p1的Age属性值是:%v,类型是:%T\n", p1.Age, p1.Age)    // 打印结果：未初始化的p1的Age属性值是:0,类型是:int
	fmt.Printf("未初始化的p1的Name属性值是:%v,类型是:%T\n", p1.Name, p1.Name) // 打印结果：未初始化的p1的Name属性值是:,类型是:string
	fmt.Printf("未初始化的p1值是:%v\n", p1)                             // 打印结果：未初始化时p1的值是:{  0}

	p1.Name = "张三"
	p1.City = "上海"
	p1.Age = 18
	fmt.Printf("初始之后的p1类型:%T, 值是:%v\n", p1, p1) // 打印结果：p1的类型:structybxb.person, 值是:{张三 上海 18}
}

// 方式2:声明结构体：普通类型显式初始化
func TestStructDefine2() {
	// 1.普通方式实例化结构体(不常用)
	var p2 person = person{} // 显式的初始化
	p2.Name = "abc"
	p2.City = "上海"
	p2.Age = 18
	fmt.Printf("p2的类型是：%T\n", p2) // p2的类型是：structybxb.person
	fmt.Printf("p2的值是：%v\n", p2)  // p2的值是：{abc 上海 18}
}

// 方式3:声明结构体：new方法
func TestStructDefine3() {
	// new方法实例化结构体
	// 注意：new方法返回的指针类型的数据，所以p3的类型是指针类型：*person
	// var p = make(person) // 注：make函数不能实例化结构体，make函数只能实例化slice、map和channel
	// fmt.Println(p)
	var p3 = new(person)          // 返回p3是指针类型的变量
	fmt.Printf("p3的类型是：%T\n", p3) // p3的类型是：*structybxb.person  # 格式是:*包名.结构体名
	p3.Name = "张三"                // 等价于:(*p3).Name = "张三",可以这样操作的原因是底层做了自动转换。
	p3.City = "上海"
	p3.Age = 18
	fmt.Printf("变量p3的类型是:%T,值是:%v\n", p3, p3)
	// 打印结果：变量p3的类型是:*structybxb.person,值是:&{张三 上海 18}。即：p3的类型是指针类型。
}

// 方式4:声明结构体：指针类型(推荐使用)
func TestStructDefin4() {
	// 指针类型，即:变量p4是指针类型
	var p4 = &person{ // &获取变量的内存地址
		"张三",
		"上海",
		18,
	}
	fmt.Printf("p4的类型是：%T\n", p4) // p4的类型是：*structybxb.person(p4是指针类型)
	fmt.Printf("p4的值是：%v\n", p4)  // p4的值是：&{张三 上海 18}
}

// 调用结构体的方法
func TestStructMethod1() {
	var p4 = &person{ // 实例化并初始化属性
		Name: "ybxb",
		City: "上海",
		Age:  18}
	p4.GetInfo() // 打印结果是：p的名字是:ybxb, 城市是:上海, 年龄是:18
}

// 测试值类型的接收器
func TestStructMethod2() {
	var p5 = &person{
		Name: "ybxb",
		City: "郑州",
		Age:  12,
	} // 普通类型，显式实例化结构体
	fmt.Println(p5) // 打印结果是：&{ybxb 郑州 12} 是初始值
	p5.SetInfo1("张三1", "上海1", 18)
	fmt.Println(p5) // 打印结果是：&{ybxb 郑州 12} 是初始值
	// 注意：两次打印p5的值都是初始值
}

// 测试指针类型的接收器
func TestStructMethod3() {
	var p5 = &person{} // 普通类型，显式实例化结构体
	fmt.Println(p5)    // 打印结果是：&{  0}
	p5.SetInfo2("张三1", "上海1", 18)
	fmt.Println(p5) // 打印结果是：&{张三1 上海1 18}
}
